/*
//	CP2013 Traffic Simulator Assignment :: (MVC) View :: SDL Visualisation Window (Function Definitions)
//	Copyright (c) 2010, Adam Rehn
//
//	This file contains the function definitions for this component.
*/
#include "visualisation.h"

//We are using strings
#include <string>

//Include the sprite resources
#include "sprite/vehicle_n_bmp.c"
#include "sprite/vehicle_s_bmp.c"
#include "sprite/vehicle_w_bmp.c"
#include "sprite/vehicle_e_bmp.c"
#include "sprite/trafficlight_ns_bmp.c"
#include "sprite/trafficlight_we_bmp.c"

//Used for rendering the traffic lights
#define TLIGHT_LED_SIZE     21
#define TLIGHT_BOX_LONGSIDE 63
#define TLIGHT_BOX_MARGIN   5

visualisationWindow::visualisationWindow(unsigned int w, unsigned int h)
{
	//Set all of the pointers to null
	streetMap = tileDirections = cars = trafficLights = vehicleSprite_N = vehicleSprite_S = vehicleSprite_W = vehicleSprite_E = NULL;
	
	//Set the cycle length to the default
	setCycleLength(DEFAULT_CYCLE_LENGTH);
	
	//Create the visualisation window
	setWindowSize(toPx(w), toPx(h));
	
	//Load the sprites from memory
	//loadSpriteResources();
	
	//Build the colours array
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_BLACK));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_WHITE));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_RED));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_BLUE));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_GREEN));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_YELLOW));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_ORANGE));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_BROWN));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_GREY));
	vechicleColours.push_back(SDL_MapRGB(screen->format, COLOUR_LIGHTGREY));
}

//Use this to reset the visualisation window size
void visualisationWindow::setWindowSize(unsigned int w, unsigned int h)
{
	//Create the visualisation window
	screen = SDL_SetVideoMode(toPx(w), toPx(h), 32, WINDOW_FLAGS);
	if (!screen)
		throw "Could not create the visualisation window!";
	
	//Set the window title
	SDL_WM_SetCaption("Visualisation Window", "Visualisation Window");
	
	//Free the layer surfaces if they already exist
	freeSurfaces(false);
	
	//Create all of the layers
	streetMap      = createTransparentSurface();
	tileDirections = createTransparentSurface();
	cars           = createTransparentSurface();
	trafficLights  = createTransparentSurface();
	if (!streetMap || !tileDirections || !cars || !trafficLights)
		throw "Could not create the visualisation layers!";
}

//Use this to set the window title
void visualisationWindow::setWindowTitle(char* title)
{
	//Set the window title
	std::string wTitle = std::string("Visualisation Window - ") + title;
	SDL_WM_SetCaption(wTitle.c_str(), wTitle.c_str());
}

void visualisationWindow::freeSurfaces(bool freeSprites)
{
	//Free all of the layer surfaces
	if (streetMap) {
		SDL_FreeSurface(streetMap);
		streetMap = NULL;
	}
	
	if (tileDirections) {
		SDL_FreeSurface(tileDirections);
		tileDirections = NULL;
	}
	
	if (cars) {
		SDL_FreeSurface(cars);
		cars = NULL;
	}
	
	if (trafficLights) {
		SDL_FreeSurface(trafficLights);
		trafficLights = NULL;
	}
	
	//Check if we are freeing the sprites
	if (freeSprites)
	{
		//Free all of the sprite surfaces
		if (vehicleSprite_N) {
			SDL_FreeSurface(vehicleSprite_N);
			vehicleSprite_N = NULL;
		}
	
		if (vehicleSprite_S) {
			SDL_FreeSurface(vehicleSprite_S);
			vehicleSprite_S = NULL;
		}
	
		if (vehicleSprite_W) {
			SDL_FreeSurface(vehicleSprite_W);
			vehicleSprite_W = NULL;
		}
	
		if (vehicleSprite_E) {
			SDL_FreeSurface(vehicleSprite_E);
			vehicleSprite_E = NULL;
		}
	}
}

visualisationWindow::~visualisationWindow()
{
	//Free the layer surfaces
	freeSurfaces(true);
}

//Use this to set the cycle length in seconds
void visualisationWindow::setCycleLength(double cycleLength)
{
	this->cycleLength = cycleLength;
}

//The view can use these to update the display
void visualisationWindow::compositeMap(mvcModel* model)
{
	//Fill the streetMap with a green background
	SDL_FillRect(streetMap, 0, SDL_MapRGB(streetMap->format, COLOUR_GREEN));
	
	//Fill the intersection itself with grey
	SDL_Rect dest;
	dest.x = toPx(model->intSct.area.x);
	dest.y = toPx(model->intSct.area.y);
	dest.w = toPx(model->intSct.area.w);
	dest.h = toPx(model->intSct.area.h);
	SDL_FillRect(streetMap, &dest, SDL_MapRGB(streetMap->format, COLOUR_GREY));
	
	//Fill all of the lanes with grey
	for (int roadN = 0; roadN < 4; ++roadN)
	{
		for (int laneN = 0; laneN < model->roads[roadN]->size(); ++laneN)
		{
			//Grab a reference to the current lane
			lane currLane = model->roads[roadN]->at(laneN);
			
			//Set the destination rectangle coordinates
			SDL_Rect dest;
			dest.x = toPx(currLane.x);
			dest.y = toPx(currLane.y);
			dest.w = toPx(currLane.w);
			dest.h = toPx(currLane.h);
			
			/*
			//The width and height are determined by the orientation of the lane
			if (currLane.direction == DIRECTION_NORTH || currLane.direction == DIRECTION_SOUTH)
			{
				dest.w = toPx(1);
				dest.h = toPx(currLane.length);
			}
			else
			{
				dest.w = toPx(currLane.length);
				dest.h = toPx(1);
			}
			*/
			
			//Fill the rectangle
			SDL_FillRect(streetMap, &dest, SDL_MapRGB(streetMap->format, COLOUR_GREY));
		}
	}
	
	//Update the dynamic layers
	updateCarsAndLights(model);
}

void visualisationWindow::updateCarsAndLights(mvcModel* model)
{
	//Clear the dynamic layers
	SDL_FillRect(tileDirections, 0, SDL_MapRGB(tileDirections->format, TRANSPARENT_COLOUR));
	SDL_FillRect(cars,           0, SDL_MapRGB(cars->format,           TRANSPARENT_COLOUR));
	SDL_FillRect(trafficLights,  0, SDL_MapRGB(trafficLights->format,  TRANSPARENT_COLOUR));
	
	/*
	//Loop through each of the tiles and set the colour according to the direction
	for (unsigned int y = 0; y < model->map->getHeight(); ++y)
	{
		for (unsigned int x = 0; x < model->map->getWidth(); ++x)
		{
			//This will hold the colour
			Uint32 colour = 0;
			
			//Grab a reference to the current tile
			direction_t currDir = model->map->at(x, y);
			//direction_t currDir = test.at(x, y);
			
			//Create the destination rectangle
			SDL_Rect dest;
			dest.x = toPx(x);
			dest.y = toPx(y);
			dest.w = toPx(1);
			dest.h = toPx(1);
			
			//if (currDir)
				//cout << "Rendering colour for (" << x << "," << y << ") which is " << currDir << endl;
			
			switch (currDir)
			{
				case DIRECTION_NORTH:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_RED);
					break;
				
				case DIRECTION_SOUTH:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_YELLOW);
					break;
				
				case DIRECTION_WEST:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_ORANGE);
					break;
				
				case DIRECTION_EAST:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_BLUE);
					break;
				
				case DIRECTION_NORTH|DIRECTION_SOUTH:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_BROWN);
					break;
				
				case DIRECTION_WEST|DIRECTION_EAST:
					colour = SDL_MapRGB(tileDirections->format, COLOUR_BROWN);
					break;
			}
			
			//Fill the tile
			if (currDir && colour)
				SDL_FillRect(tileDirections, &dest, colour);
		}
	}
	*/
	
	//Highlight all of the car spawn points and intersection entry points
	for (int roadN = 0; roadN < 4; ++roadN)
	{
		for (int laneN = 0; laneN < model->roads[roadN]->size(); ++laneN)
		{
			//Grab a reference to the current lane's spawn point
			coordinate spawnPoint = model->roads[roadN]->at(laneN).spawnPoint;
			
			//cout << "Spawn point at (" << spawnPoint.x << "," << spawnPoint.y << ")" << endl;
			
			//Set the destination rectangle coordinates
			SDL_Rect dest;
			dest.x = toPx(spawnPoint.x);
			dest.y = toPx(spawnPoint.y);
			dest.w = toPx(1);
			dest.h = toPx(1);
			
			//Fill the rectangle
			SDL_FillRect(tileDirections, &dest, SDL_MapRGB(tileDirections->format, COLOUR_WHITE));
			
			//Grab a reference to the current lane's intersection entry point
			coordinate entryPoint = model->roads[roadN]->at(laneN).intersectionEntryPoint;
			
			//Only highlight if the lane actually has an intersection entry point
			if (entryPoint != coordinate(0,0))
			{
				//Set the destination rectangle coordinates
				dest.x = toPx(entryPoint.x);
				dest.y = toPx(entryPoint.y);
				dest.w = toPx(1);
				dest.h = toPx(1);
				
				//Fill the rectangle
				SDL_FillRect(cars, &dest, SDL_MapRGB(cars->format, COLOUR_MEDIUMGREY));
			}
		}
	}
	
	//Loop through all of the vehicles
	for (int i = 0; i < model->vehicles.size(); ++i)
	{
		//Grab a reference to the vehicle
		vehicle currVehicle = model->vehicles[i];
		
		//Render the vehicle's sprite
		renderVehicleSprite(currVehicle);
	}
	
	//Render the traffic lights
	renderTrafficLights(model, DIRECTION_NORTH);
	renderTrafficLights(model, DIRECTION_SOUTH);
	renderTrafficLights(model, DIRECTION_WEST);
	renderTrafficLights(model, DIRECTION_EAST);
}

void visualisationWindow::renderToScreen(clock_t start, unsigned int i, unsigned int t)
{
	//Create the source and destination rectangles
	SDL_Rect src, dest;
	src.x = 0;
	src.y = 0;
	src.w = screen->w;
	src.h = screen->h;
	dest = src;
	
	//Blit all of the compositing layers to the screen's buffer
	SDL_BlitSurface(streetMap, &src, screen, &dest);
	//SDL_BlitSurface(tileDirections, &src, screen, &dest);
	SDL_BlitSurface(cars, &src, screen, &dest);
	SDL_BlitSurface(trafficLights, &src, screen, &dest);
	
	//If a cycle number and total were supplied, use them in the window title
	if (i > 0 && t > 0)
	{
		std::string newTitle = std::string("Visualisation Window - Cycle " + toString(i) + " of " + toString(t));
		SDL_WM_SetCaption(newTitle.c_str(), newTitle.c_str());
	}
	//else
	//	SDL_WM_SetCaption("Visualisation Window", "Visualisation Window");
	
	//Flush the screen's buffer, presenting the new screen to the user
	SDL_Flip(screen);
	
	//If a start time was supplied, we can slow things down a notch
	if (start)
	{
		//Calculate the desired finish time
		clock_t finish = start + static_cast<clock_t>(this->cycleLength * static_cast<double>(CLOCKS_PER_SEC));
		
		//Wait until the desired time has been reached
		while (clock() < finish)
		{
			//Pump events to keep the program responding
			SDL_PumpEvents();
		}
	}
}

//Helper functions
SDL_Surface* visualisationWindow::createTransparentSurface()
{
	//If there is no visualisation window open, we won't know the right width and height
	if (!screen) return NULL;
	
	//Create a new surface
	SDL_Surface* newSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, screen->h, 32, 0, 0, 0, 0);
	
	//If the surface could not be created, return a null pointer
	if (!newSurface) return NULL;
	
	//Set the colour key for transparency
	SDL_SetColorKey(newSurface, SDL_SRCCOLORKEY, SDL_MapRGB(newSurface->format, TRANSPARENT_COLOUR));
	
	//Fill the surface with a transparent background
	SDL_FillRect(newSurface, 0, SDL_MapRGB(newSurface->format, TRANSPARENT_COLOUR));
	
	//Return the new surface
	return newSurface;
}

unsigned int visualisationWindow::toPx(unsigned int i)
{
	return i * UNIT_TRANSLATION;
}

void visualisationWindow::loadSpriteResources()
{
	cout << "Creating vehicleSprite_N..." << endl;
	vehicleSprite_N = createTransparentSurfaceFromResource(VEHICLE_N_BMP, SIZEOF_VEHICLE_N_BMP);

	cout << "Creating vehicleSprite_S..." << endl;
	vehicleSprite_S = createTransparentSurfaceFromResource(VEHICLE_S_BMP, SIZEOF_VEHICLE_S_BMP);

	cout << "Creating vehicleSprite_W..." << endl;
	vehicleSprite_W = createTransparentSurfaceFromResource(VEHICLE_W_BMP, SIZEOF_VEHICLE_W_BMP);


	cout << "Creating vehicleSprite_E..." << endl;
	vehicleSprite_E = createTransparentSurfaceFromResource(VEHICLE_E_BMP, SIZEOF_VEHICLE_E_BMP);
	
	cout << "Creating trafficlightSprite_NS..." << endl;
	trafficlightSprite_NS = createTransparentSurfaceFromResource(TRAFFICLIGHT_NS_BMP, SIZEOF_TRAFFICLIGHT_NS_BMP);
	
	cout << "Creating trafficlightSprite_WE..." << endl;
	trafficlightSprite_WE = createTransparentSurfaceFromResource(TRAFFICLIGHT_WE_BMP, SIZEOF_TRAFFICLIGHT_WE_BMP);
}

SDL_Surface* visualisationWindow::createTransparentSurfaceFromResource(const char *ptr, size_t size)
{
	//Use an SDL_RWops structure to facilitate reading from memory
	SDL_RWops *rw = SDL_RWFromMem((void*)ptr, size);
	
	//If the RWops could not be created, return a null pointer
	if (!rw) return NULL;
	
	//Load the bitmap from the structure
	SDL_Surface *newSurface = SDL_LoadBMP_RW(rw, false);
	
	//If the surface could not be created, return a null pointer
	if (!newSurface) return NULL;
	
	//Set the colour key for transparency
	SDL_SetColorKey(newSurface, SDL_SRCCOLORKEY, SDL_MapRGB(newSurface->format, COLOUR_RED));
	
	//Free the RWops structure
	SDL_FreeRW(rw);
	
	//Return the new surface
	return newSurface;
}

void visualisationWindow::renderVehicleSprite(vehicle& currVehicle)
{
	//Set the destination rectangle coordinates
	SDL_Rect dest;
	dest.x = toPx(currVehicle.x);
	dest.y = toPx(currVehicle.y);
	dest.w = toPx(currVehicle.w);
	dest.h = toPx(currVehicle.h);
	
	/*
	//The width and height are determined by the orientation of the vehicle
	if (currVehicle.direction == DIRECTION_NORTH || currVehicle.direction == DIRECTION_SOUTH)
	{
		dest.w = toPx(1);
		dest.h = toPx(currVehicle.length);
	}
	else
	{
		dest.w = toPx(currVehicle.length);
		dest.h = toPx(1);
	}
	*/
	
	//Determine the appropriate sprite based on the vehicle's direction
	SDL_Surface *vehicleSprite = NULL;
	
	if (currVehicle.direction == DIRECTION_NORTH)
		vehicleSprite = vehicleSprite_N;
	else if (currVehicle.direction == DIRECTION_SOUTH)
		vehicleSprite = vehicleSprite_S;
	else if (currVehicle.direction == DIRECTION_WEST)
		vehicleSprite = vehicleSprite_W;
	else if (currVehicle.direction == DIRECTION_EAST)
		vehicleSprite = vehicleSprite_E;
	
	/*
	switch (currVehicle.direction)
	{
		case DIRECTION_NORTH:
			vehicleSprite = vehicleSprite_N;
			break;
		
		case DIRECTION_SOUTH:
			vehicleSprite = vehicleSprite_S;
			break;
		
		case DIRECTION_WEST:
			vehicleSprite = vehicleSprite_W;
			break;
		
		case DIRECTION_EAST:
			vehicleSprite = vehicleSprite_E;
			break;
	}
	*/
	
	//Create a new surface to composite the sprite with the vehicle's colour
	SDL_Surface *compositedSprite = SDL_CreateRGBSurface(SDL_SWSURFACE, dest.w, dest.h, 32, 0, 0, 0, 0);
	
	//If the compositing surface could not be created, simply blit the base sprite
	if (!compositedSprite)
	{
		//Blit the base sprite
		SDL_BlitSurface(vehicleSprite, NULL, cars, &dest);
	}
	else
	{
		//Set the compositing layer's colour key
		SDL_SetColorKey(compositedSprite, SDL_SRCCOLORKEY, SDL_MapRGB(compositedSprite->format, TRANSPARENT_COLOUR));
		
		//Fill the compositing layer with the vehicle's colour
		SDL_FillRect(compositedSprite, 0, vechicleColours[currVehicle.colour % vechicleColours.size()]);
		
		//Blit the base sprite onto the compositing layer
		SDL_BlitSurface(vehicleSprite, NULL, compositedSprite, NULL);
		
		//Blit the composited sprite onto the screen
		SDL_BlitSurface(compositedSprite, NULL, cars, &dest);
		
		//Free the surface
		SDL_FreeSurface(compositedSprite);
	}
}

void visualisationWindow::renderTrafficLights(mvcModel* model, direction_t set)
{
	//Determine which set of lights to load the sprite for
	SDL_Surface *lightsSprite = NULL;
	if (set == DIRECTION_NORTH || set == DIRECTION_SOUTH)
		lightsSprite = trafficlightSprite_NS;
	else
		lightsSprite = trafficlightSprite_WE;
	
	//Create the compositing layer
	SDL_Surface *compositedSprite = SDL_CreateRGBSurface(SDL_SWSURFACE, lightsSprite->w, lightsSprite->h, 32, 0, 0, 0, 0);
	
	//If the compositing layer could not be created, exit immediately
	if (!compositedSprite)
		return;
	
	//Fill the compositing layer with black
	SDL_FillRect(compositedSprite, 0, SDL_MapRGB(compositedSprite->format, COLOUR_BLACK));
	
	//Temporary variables
	Uint32 colour = 0;
	lightstate state;
	
	//Determine the state of the lights
	if (set == DIRECTION_NORTH || set == DIRECTION_SOUTH)
		state = model->intSct.lightNS.state;
	else
		state = model->intSct.lightWE.state;
	
	//Determine the colour of the lights to fill
	switch (state)
	{
		case RED:
			colour = SDL_MapRGB(compositedSprite->format, COLOUR_RED);
			break;
		
		case YELLOW:
			colour = SDL_MapRGB(compositedSprite->format, COLOUR_YELLOW);
			break;
		
		case GREEN:
			colour = SDL_MapRGB(compositedSprite->format, COLOUR_GREEN);
			break;
	}
	
	//Create the destination rectangle for compositing
	SDL_Rect compositeDest;
	compositeDest.x = 0;
	compositeDest.y = 0;
	compositeDest.w = TLIGHT_LED_SIZE;
	compositeDest.h = TLIGHT_LED_SIZE;
	
	//Determine the orientation of the lights
	if (set == DIRECTION_NORTH)
	{
		compositeDest.y += (TLIGHT_LED_SIZE * state);
		compositeDest.w = TLIGHT_BOX_LONGSIDE;
	}
	else if (set == DIRECTION_SOUTH)
	{
		compositeDest.y = compositedSprite->h - (TLIGHT_LED_SIZE * (state+1));
		compositeDest.w = TLIGHT_BOX_LONGSIDE;
	}
	else if (set == DIRECTION_WEST)
	{
		compositeDest.x = compositedSprite->w - (TLIGHT_LED_SIZE * (state+1));
		compositeDest.h = TLIGHT_BOX_LONGSIDE;
	}
	else
	{
		compositeDest.x += (TLIGHT_LED_SIZE * state);
		compositeDest.h = TLIGHT_BOX_LONGSIDE;
	}
	
	//Composite the sprite
	SDL_FillRect(compositedSprite, &compositeDest, colour);
	SDL_BlitSurface(lightsSprite, NULL, compositedSprite, NULL);
	
	//Create the destination rectangle for blitting to the cars layer
	SDL_Rect dest;
	dest.w = compositedSprite->w;
	dest.h = compositedSprite->h;
	dest.x = 0;
	dest.y = 0;
	
	//Grab a reference to the intersection
	intersection intSct = model->intSct;
	
	//Determine the location of the lights based on which set
	if (set == DIRECTION_NORTH)
	{
		dest.y = toPx(intSct.area.y) + (toPx(intSct.area.h) + TLIGHT_BOX_MARGIN);
		dest.x = toPx(intSct.area.x) + (toPx(intSct.area.w) + TLIGHT_BOX_MARGIN);
	}
	else if (set == DIRECTION_SOUTH)
	{
		dest.y = toPx(intSct.area.y) - (compositedSprite->h + TLIGHT_BOX_MARGIN);
		dest.x = toPx(intSct.area.x) - (compositedSprite->w + TLIGHT_BOX_MARGIN);
	}
	else if (set == DIRECTION_WEST)
	{
		dest.y = toPx(intSct.area.y) + (toPx(intSct.area.h) + TLIGHT_BOX_MARGIN);
		dest.x = toPx(intSct.area.x) - (compositedSprite->w + TLIGHT_BOX_MARGIN);
	}
	else
	{
		dest.y = toPx(intSct.area.y) - (compositedSprite->h + TLIGHT_BOX_MARGIN);
		dest.x = toPx(intSct.area.x) + (toPx(intSct.area.w) + TLIGHT_BOX_MARGIN);
	}
	
	//Blit the composited sprite to the cars layer
	SDL_BlitSurface(compositedSprite, NULL, cars, &dest);
}
